package br.edu.ufcg.ga.parser.args;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.jgap.InvalidConfigurationException;

import br.edu.ufcg.ga.CVConfiguration;
import br.edu.ufcg.ga.CaixeiroViajante;
import br.edu.ufcg.ga.parser.args.group.GOgreedyCrossoverOperatorGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.GOswappingMutationOperatorGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.MyGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.NSbestChromosomesSelectorGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.NStournamentSelectorGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.NSweightedRouletteSelectorGroupBuilder;
import br.edu.ufcg.ga.parser.args.group.SimulationGroupBuilder;

public class ArgsParser {

	private static boolean ASSERTIONS = true;

	private Options options;

	private List<MyGroupBuilder> groupBuilders;

	public ArgsParser() {
		this.options = new Options();
		this.groupBuilders = new ArrayList<MyGroupBuilder>();
	}

	public void init() {
		this.groupBuilders.add(new NSbestChromosomesSelectorGroupBuilder());
		this.groupBuilders.add(new NSweightedRouletteSelectorGroupBuilder());
		this.groupBuilders.add(new NStournamentSelectorGroupBuilder());
		this.groupBuilders.add(new GOgreedyCrossoverOperatorGroupBuilder());
		this.groupBuilders.add(new GOswappingMutationOperatorGroupBuilder());
		this.groupBuilders.add(new SimulationGroupBuilder());
		addOptions(this.options, this.groupBuilders);
	}

	private void addOptions(Options options, List<MyGroupBuilder> groupBuilders) {
		for (MyGroupBuilder groupBuilder : groupBuilders) {
			for (Object option : groupBuilder.getOptions()) {
				options.addOption((Option) option);
			}
		}

		if (ASSERTIONS) {
			ensureOptionsWithDifferentNames();
		}
	}

	private void ensureOptionsWithDifferentNames() {
		Set<String> longOpts = new HashSet<String>();
		Set<String> opts = new HashSet<String>();
		for (Object obj : this.options.getOptions()) {
			Option option = (Option) obj;

			String longOpt = option.getLongOpt();
			String opt = option.getOpt();

			if (longOpt != null && longOpts.contains(longOpt)) {
				throw new RuntimeException("Options with same <longOpt> found!");
			}

			if (opt != null && opts.contains(opt)) {
				throw new RuntimeException("Options with same <opt> found!");
			}

			longOpts.add(longOpt);
			opts.add(opt);

		}
	}

	public CVConfiguration parseArgs(String[] args) throws ParseException,
			InvalidConfigurationException {
		if (args.length == 0) {
			throw new ParseException(
					"ERROR! Missing argument(s)! Example: br.edu.ufcg.ga.CaixeiroViajante -NS_TS -TS_SR=0.9 -TS_TS=3 -GO_GCO -GO_SMO -SMO_MR=0.05");
		}

		CommandLineParser parser = new GnuParser();
		CommandLine commandLine = null;

		commandLine = parser.parse(this.options, args);
		String[] leftArgs = commandLine.getArgs();

		if (leftArgs.length != 0) {
			throw new ParseException("ERROR! Error! Unknown argument(s): "
					+ Arrays.toString(leftArgs));
		}

		return getConfiguration(commandLine);
	}

	private CVConfiguration getConfiguration(CommandLine commandLine)
			throws InvalidConfigurationException, ParseException {
		CVConfiguration conf = new CVConfiguration();

		// TODO Varrer os groupBuilders de acordo com a ordem que eles aparecem
		// no commandLine
		// Isto vai influenciar a forma como os Seletores Naturais e Operadores
		// Genéticos são acionados

		for (MyGroupBuilder builder : this.groupBuilders) {
			builder.config(conf, commandLine);
		}

		return conf;
	}

	public void printUsage() {
		this.printUsage(this.options);
	}

	private void printUsage(Options options) {
		HelpFormatter hf = new HelpFormatter();
		int width = 120;
		String header = getString("=", width);
		hf.printHelp(width, CaixeiroViajante.class.getCanonicalName(), header,
				options, header, true);
	}

	private String getString(String chaR, int width) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < width; i++) {
			sb.append(chaR);
		}
		return sb.toString();
	}

}
